﻿using ImageK.Gui;
using ImageK.Measure;
using ImageK.Process;
using ImageK;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Packaging;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ImageK.Plugin;
using System.Windows.Forms.VisualStyles;
using Microsoft.VisualBasic.Logging;
using System.Drawing;

namespace ImageK.Plugin
{
    /** This plugin implements the Analyze/Tools/Scale Bar command.
     * Divakar Ramachandran added options to draw a background 
     * and use a serif font on 23 April 2006.
     * Remi Berthoz added an option to draw vertical scale
     * bars on 17 September 2021.
*/
    public class ScaleBar : PlugIn
    {
        public string[] locations = new string[] {"Upper Right", "Lower Right", "Lower Left", "Upper Left", "At Selection"};
        public const int UPPER_RIGHT = 0, LOWER_RIGHT = 1, LOWER_LEFT = 2, UPPER_LEFT = 3, AT_SELECTION = 4;
        public string[] colors = new string[] { "White","Black","Light Gray","Gray","Dark Gray","Red","Green","Blue","Yellow"};
        public string[] bcolors = new string[] { "None","Black","White","Dark Gray","Gray","Light Gray","Yellow","Blue","Green","Red"};
        public string[] checkboxLabels = new string[] { "Horizontal", "Vertical", "Bold Text", "Hide Text", "Serif Font", "Overlay"};
        static string SCALE_BAR = "|SB|";

        private ScaleBarConfiguration sConfig;
        public ScaleBarConfiguration config;

        ImagePlus imp;
        int hBarWidthInPixels;
        int vBarHeightInPixels;
        int roiX, roiY, roiWidth, roiHeight;
        bool userRoiExists;
        public bool[] checkboxStates = new bool[6];

        Rectangle hBackground = new Rectangle();
        Rectangle hBar = new Rectangle();
        Rectangle hText = new Rectangle();
        Rectangle vBackground = new Rectangle();
        Rectangle vBar = new Rectangle();
        Rectangle vText = new Rectangle();

        public ScaleBar()
        {
            sConfig = new ScaleBarConfiguration(this);
            config = new ScaleBarConfiguration(sConfig, this);
        }

        /**
         * This method is called when the plugin is loaded. 'arg', which
         * may be blank, is the argument specified for this plugin in
         * IJ_Props.txt.
         */
        public void run(String arg)
        {
            imp = WindowManager.getCurrentImage();
            if (imp == null)
            {
                IJ.noImage();
                return;
            }
            // Snapshot before anything, so we can revert if the user cancels the action.
            imp.getProcessor().snapshot();

            userRoiExists = parseCurrentROI();
            bool userOKed = askUserConfiguration(userRoiExists);

            if (!userOKed)
            {
                removeScalebar();
                return;
            }

            if (!IJ.isMacro())
                persistConfiguration();
            updateScalebar(!config.labelAll);
        }

        /**
         * Remove the scalebar drawn by this plugin.
         * 
         * If the scalebar was drawn without the overlay by another
         * instance of the plugin (it is drawn into the image), then
         * we cannot remove it.
         * 
         * If the scalebar was drawn using the overlay by another
         * instance of the plugin, then we can remove it.
         * 
         * With or without the overlay, we can remove a scalebar
         * drawn by this instance of the plugin.
         */
        void removeScalebar()
        {
            // Revert with Undo, in case "Use Overlay" is not ticked
            imp.getProcessor().reset();
            imp.updateAndDraw();
            // Remove overlay drawn by this plugin, in case "Use Overlay" is ticked
            Overlay overlay = imp.getOverlay();
            if (overlay!=null)
            {
                overlay.remove(SCALE_BAR);
                imp.draw();
            }
        }

        /**
         * If there is a user selected ROI, set the class variables {roiX}
         * and {roiY}, {roiWidth}, {roiHeight} to the corresponding
         * features of the ROI, and return true. Otherwise, return false.
         */
        bool parseCurrentROI()
        {
            Roi roi = imp.getRoi();
            if (roi == null) return false;

            Rectangle r = roi.getBounds();
            roiX = r.X;
            roiY = r.Y;
            roiWidth = r.Width;
            roiHeight = r.Height;
            return true;
        }

        /**
         * There is no hard codded value for the width of the scalebar,
         * when the plugin is called for the first time in an ImageJ
         * instance, a defautl value for the width will be computed by
         * this method.
         */
        void computeDefaultBarWidth(bool currentROIExists)
        {
            Calibration cal = imp.getCalibration();
            ImageWindow win = imp.getWindow();
            double mag = (win!=null) ? win.getCanvas().getMagnification() : 1.0;
            if (mag>1.0)
                mag = 1.0;

            double pixelWidth = cal.pixelWidth;
            if (pixelWidth==0.0)
                pixelWidth = 1.0;
            double pixelHeight = cal.pixelHeight;
            if (pixelHeight==0.0)
                pixelHeight = 1.0;
            double imageWidth = imp.getWidth()*pixelWidth;
            double imageHeight = imp.getHeight()*pixelHeight;

            if (currentROIExists && roiX>=0 && roiWidth>10)
            {
                // If the user has a ROI, set the bar width according to ROI width.
                config.hBarWidth = roiWidth*pixelWidth;
            }
            else if (config.hBarWidth<=0.0 || config.hBarWidth>0.67*imageWidth)
            {
                // If the bar is of negative width or too wide for the image,
                // set the bar width to 80 pixels.
                config.hBarWidth = (80.0*pixelWidth)/mag;
                if (config.hBarWidth>0.67*imageWidth)
                    // If 80 pixels is too much, do 2/3 of the image.
                    config.hBarWidth = 0.67*imageWidth;
                if (config.hBarWidth>5.0)
                    // If the resulting size is larger than 5 units, round the value.
                    config.hBarWidth = (int)config.hBarWidth;
            }

            if (currentROIExists && roiY>=0 && roiHeight>10)
            {
                config.vBarHeight = roiHeight*pixelHeight;
            }
            else if (config.vBarHeight<=0.0 || config.vBarHeight>0.67*imageHeight)
            {
                config.vBarHeight = (80.0*pixelHeight)/mag;
                if (config.vBarHeight>0.67*imageHeight)
                    // If 80 pixels is too much, do 2/3 of the image.
                    config.vBarHeight = 0.67*imageHeight;
                if (config.vBarHeight>5.0)
                    // If the resulting size is larger than 5 units, round the value.
                    config.vBarHeight = (int)config.vBarHeight;
            }
        }

        /**
         * Genreate & draw the configuration dialog.
         * 
         * Return the value of dialog.wasOKed() when the user clicks OK
         * or Cancel.
         */
        bool askUserConfiguration(bool currentROIExists)
        {
            // Update the user configuration if there is an ROI, or if
            // the defined bar width is negative (it is if it has never
            // been set in this ImageJ instance).
            if (currentROIExists)
            {
                config.location = locations[AT_SELECTION];
            }
            if (IJ.isMacro())
                config.updateFrom(new ScaleBarConfiguration(this));
            if (config.hBarWidth <= 0 || config.vBarHeight <= 0 || currentROIExists)
            {
                computeDefaultBarWidth(currentROIExists);
            }

            // Draw a first preview scalebar, with the default or presisted
            // configuration.
            updateScalebar(true);

            // Create & show the dialog, then return.
            bool multipleSlices = imp.getStackSize() > 1;
            GenericDialog dialog = new BarDialog(getHUnit(), getVUnit(), config.hDigits, config.vDigits, multipleSlices, this);
            DialogListener dialogListener = new BarDialogListener(multipleSlices, this);
            dialog.addDialogListener(dialogListener);
            dialog.showDialog();
            return dialog.wasOKed();
        }

        /**
         * Store the active configuration into the static variable that
         * is persisted across calls of the plugin.
         * 
         * The "active" configuration is normally the one reflected by
         * the dialog.
         */
        void persistConfiguration()
        {
            sConfig.updateFrom(config);
        }

        /**
         * Return the X unit strings defined in the image calibration.
         */
        string getHUnit()
        {
            string hUnits = imp.getCalibration().getXUnits();
            if (hUnits.Equals("microns"))
                hUnits = IJ.micronSymbol+"m";
            return hUnits;
        }

        /**
         * Return the Y unit strings defined in the image calibration.
         */
        string getVUnit()
        {
            string vUnits = imp.getCalibration().getYUnits();
            if (vUnits.Equals("microns"))
                vUnits = IJ.micronSymbol+"m";
            return vUnits;
        }

        /**
         * Create & draw the scalebar using an Overlay.
         */
        Overlay createScaleBarOverlay()
        {
            Overlay overlay = new Overlay();

            Color color = getColor();
            Color bcolor = getBColor();

            FontStyle fontType = config.boldText ? FontStyle.Bold : FontStyle.Regular;
            string face = config.serifFont ? "Serif" : "SanSerif";
            // Font font = new Font(face, fontType, config.fontSize);
            Font font = new Font(face, config.fontSize, fontType);
            ImageProcessor ip = imp.getProcessor();
            ip.setFont(font);

            setElementsPositions(ip);

            if (bcolor != Color.Empty)
            {
                if (config.showHorizontal)
                {
                    Roi hBackgroundRoi = new Roi(hBackground.X, hBackground.Y, hBackground.Width, hBackground.Height);
                    hBackgroundRoi.setFillColor(bcolor);
                    overlay.add(hBackgroundRoi, SCALE_BAR);
                }
                if (config.showVertical)
                {
                    Roi vBackgroundRoi = new Roi(vBackground.X, vBackground.Y, vBackground.Width, vBackground.Height);
                    vBackgroundRoi.setFillColor(bcolor);
                    overlay.add(vBackgroundRoi, SCALE_BAR);
                }
            }

            if (config.showHorizontal)
            {
                Roi hBarRoi = new Roi(hBar.X, hBar.Y, hBar.Width, hBar.Height);
                hBarRoi.setFillColor(color);
                overlay.add(hBarRoi, SCALE_BAR);
            }
            if (config.showVertical)
            {
                Roi vBarRoi = new Roi(vBar.X, vBar.Y, vBar.Width, vBar.Height);
                vBarRoi.setFillColor(color);
                overlay.add(vBarRoi, SCALE_BAR);
            }

            if (!config.hideText)
            {
                if (config.showHorizontal)
                {
                    TextRoi hTextRoi = new TextRoi(hText.X, hText.Y, getHLabel(), font);
                    hTextRoi.setStrokeColor(color);
                    overlay.add(hTextRoi, SCALE_BAR);
                }
                if (config.showVertical)
                {
                    TextRoi vTextRoi = new TextRoi(vText.X, vText.Y + vText.Height, getVLabel(), font);
                    vTextRoi.setStrokeColor(color);
                    vTextRoi.setAngle(90.0);
                    overlay.add(vTextRoi, SCALE_BAR);
                }
            }

            return overlay;
	    }

	    /**
	     * Returns the text to draw near the scalebar (<width> <unit>).
	     */
	    string getHLabel()
        {
            return IJ.d2s(config.hBarWidth, config.hDigits) + " " + getHUnit();
        }

        /**
         * Returns the text to draw near the scalebar (<height> <unit>).
         */
        string getVLabel()
        {
            return IJ.d2s(config.vBarHeight, config.vDigits) + " " + getVUnit();
        }

        /**
         * Returns the width of the box that contains the horizontal scalebar and
         * its label.
         */
        int getHBoxWidthInPixels()
        {
            updateFont();
            ImageProcessor ip = imp.getProcessor();
            int hLabelWidth = config.hideText ? 0 : ip.getStringWidth(getHLabel());
            int hBoxWidth = Math.Max(hBarWidthInPixels, hLabelWidth);
            return (config.showHorizontal ? hBoxWidth : 0);
        }

        /**
         * Returns the height of the box that contains the horizontal scalebar and
         * its label.
         */
        int getHBoxHeightInPixels()
        {
            int hLabelHeight = config.hideText ? 0 : config.fontSize;
            int hBoxHeight = config.barThicknessInPixels + (int)(hLabelHeight * 1.25);
            return (config.showHorizontal ? hBoxHeight : 0);
        }

        /**
         * Returns the height of the box that contains the vertical scalebar and
         * its label.
         */
        int getVBoxHeightInPixels()
        {
            updateFont();
            ImageProcessor ip = imp.getProcessor();
            int vLabelHeight = config.hideText ? 0 : ip.getStringWidth(getVLabel());
            int vBoxHeight = Math.Max(vBarHeightInPixels, vLabelHeight);
            return (config.showVertical ? vBoxHeight : 0);
        }

        /**
         * Returns the width of the box that contains the vertical scalebar and
         * its label.
         */
        int getVBoxWidthInPixels()
        {
            int vLabelWidth = config.hideText ? 0 : config.fontSize;
            int vBoxWidth = config.barThicknessInPixels + (int)(vLabelWidth * 1.25);
            return (config.showVertical ? vBoxWidth : 0);
        }

        /**
         * Returns the size of margins that should be displayed between the scalebar
         * elements and the image edge.
         */
        int getOuterMarginSizeInPixels()
        {
            int imageWidth = imp.getWidth();
            int imageHeight = imp.getHeight();
            return (imageWidth + imageHeight) / 100;
        }

        /**
         * Retruns the size of margins that should be displayed between the scalebar
         * elements and the edge of the element's backround.
         */
        int getInnerMarginSizeInPixels()
        {
            int maxWidth = Math.Max(getHBoxWidthInPixels(), getVBoxHeightInPixels());
            int margin = Math.Max(maxWidth/20, 2);
            return config.bcolor.Equals("None") ? 0 : margin;
        }

        void updateFont()
        {
            FontStyle fontType = config.boldText ? FontStyle.Bold : FontStyle.Regular;
            string font = config.serifFont ? "Serif" : "SanSerif";
            ImageProcessor ip = imp.getProcessor();
            ip.setFont(new Font(font, config.fontSize ,fontType));
            ip.setAntialiasedText(true);
        }

        /**
         * Sets the positions x y of hBackground and vBackground based on
         * the current configuration.
         */
        void setBackgroundBoxesPositions(ImageProcessor ip)
        {
            Calibration cal = imp.getCalibration();
            hBarWidthInPixels = (int)Math.Round(config.hBarWidth/cal.pixelWidth);
            vBarHeightInPixels = (int)Math.Round(config.vBarHeight/cal.pixelHeight);

            bool hTextTop = config.showVertical && (config.location.Equals(locations [UPPER_LEFT]) || config.location.Equals(locations [UPPER_RIGHT]));
		        
		    int imageWidth = imp.getWidth();
		    int imageHeight = imp.getHeight();
		    int hBoxWidth = getHBoxWidthInPixels();
		    int hBoxHeight = getHBoxHeightInPixels();
		    int vBoxWidth = getVBoxWidthInPixels();
		    int vBoxHeight = getVBoxHeightInPixels();
		    int outerMargin = getOuterMarginSizeInPixels();
		    int innerMargin = getInnerMarginSizeInPixels();

            hBackground.Width = innerMargin + hBoxWidth + innerMargin;
            hBackground.Height = innerMargin + hBoxHeight + innerMargin;
            vBackground.Width = innerMargin + vBoxWidth + innerMargin;
            vBackground.Height = innerMargin + vBoxHeight + innerMargin;

		    if (config.location.Equals(locations [UPPER_RIGHT])) {
                hBackground.X = imageWidth - outerMargin - innerMargin - vBoxWidth + (config.showVertical ? config.barThicknessInPixels : 0) - hBoxWidth - innerMargin;
                hBackground.Y = outerMargin;
                vBackground.X = imageWidth - outerMargin - innerMargin - vBoxWidth - innerMargin;
                vBackground.Y = outerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
                hBackground.Width += (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);

            } else if (config.location.Equals(locations [LOWER_RIGHT])) {
                hBackground.X = imageWidth - outerMargin - innerMargin - vBoxWidth - hBoxWidth + (config.showVertical ? config.barThicknessInPixels : 0) - innerMargin;
                hBackground.Y = imageHeight - outerMargin - innerMargin - hBoxHeight - innerMargin;
                vBackground.X = imageWidth - outerMargin - innerMargin - vBoxWidth - innerMargin;
                vBackground.Y = imageHeight - outerMargin - innerMargin - hBoxHeight + (config.showHorizontal ? config.barThicknessInPixels : 0) - vBoxHeight - innerMargin;
                vBackground.Height += (config.showHorizontal ? hBoxHeight - config.barThicknessInPixels : 0);

            } else if (config.location.Equals(locations [UPPER_LEFT])) {
                hBackground.X = outerMargin + (config.showVertical ? vBackground.Width - 2*innerMargin - config.barThicknessInPixels : 0);
                hBackground.Y = outerMargin;
                vBackground.X = outerMargin;
                vBackground.Y = outerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
                hBackground.Width += (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);
                hBackground.X -= (config.showVertical ? vBoxWidth - config.barThicknessInPixels : 0);

            } else if (config.location.Equals(locations [LOWER_LEFT])) {
                hBackground.X = outerMargin + (config.showVertical ? vBackground.Width - 2*innerMargin - config.barThicknessInPixels : 0);
                hBackground.Y = imageHeight - outerMargin - innerMargin - hBoxHeight - innerMargin;
                vBackground.X = outerMargin;
                vBackground.Y = imageHeight - outerMargin - innerMargin - hBoxHeight + (config.showHorizontal ? config.barThicknessInPixels : 0) - vBoxHeight - innerMargin;
                vBackground.Height += (config.showHorizontal ? hBoxHeight - config.barThicknessInPixels : 0);

            } else {
                if (!userRoiExists)
                    throw new MissingRoiException();

                hBackground.X = roiX;
                hBackground.Y = roiY;
                vBackground.X = roiX;
                vBackground.Y = roiY;
            }
        }

        /**
         * Sets the rectangles x y positions for scalebar elements (hBar, hText, vBar, vText),
         * based on the current configuration. Also sets the width and height of the rectangles.
         * 
         * The position of each rectangle is relative to hBackground and vBackground,
         * so setBackgroundBoxesPositions() must run before this method computes positions.
         * This method calls setBackgroundBoxesPositions().
         */
        void setElementsPositions(ImageProcessor ip)
        {

            setBackgroundBoxesPositions(ip);

		    int hBoxWidth = getHBoxWidthInPixels();
		    int hBoxHeight = getHBoxHeightInPixels();

		    int vBoxWidth = getVBoxWidthInPixels();
		    int vBoxHeight = getVBoxHeightInPixels();

		    int innerMargin = getInnerMarginSizeInPixels();

            bool right = config.location.Equals(locations [LOWER_RIGHT]) || config.location.Equals(locations [UPPER_RIGHT]);
            bool upper = config.location.Equals(locations [UPPER_RIGHT]) || config.location.Equals(locations [UPPER_LEFT]);
            bool hTextTop = config.showVertical && upper;

            hBar.X = hBackground.X + innerMargin + (hBoxWidth - hBarWidthInPixels)/2 + (config.showVertical && !right && upper ? vBoxWidth - config.barThicknessInPixels : 0);
            hBar.Y = hBackground.Y + innerMargin + (hTextTop ? hBoxHeight - config.barThicknessInPixels : 0);
            hBar.Width = hBarWidthInPixels;
            hBar.Height = config.barThicknessInPixels;

            hText.Height = config.hideText ? 0 : config.fontSize;
            hText.Width = config.hideText ? 0 : ip.getStringWidth(getHLabel());
            hText.X = hBackground.X + innerMargin + (hBoxWidth - hText.Width)/2 + (config.showVertical && !right && upper ? vBoxWidth - config.barThicknessInPixels : 0);
            hText.Y = hTextTop ? (hBackground.Y + innerMargin - (int)Math.Round(config.fontSize*0.25)) : (hBar.Y + hBar.Height);

            vBar.Width = config.barThicknessInPixels;
            vBar.Height = vBarHeightInPixels;
            vBar.X = vBackground.X + (right ? innerMargin : vBackground.Width - config.barThicknessInPixels - innerMargin);
            vBar.Y = vBackground.Y + innerMargin + (vBoxHeight - vBar.Height)/2;

            vText.Height = config.hideText ? 0 : ip.getStringWidth(getVLabel());
            vText.Width = config.hideText ? 0 : config.fontSize;
            vText.X = right ? (vBar.X + vBar.Width) : (vBar.X - vBoxWidth + config.barThicknessInPixels - (int)Math.Round(config.fontSize*0.25));
            vText.Y = vBackground.Y + innerMargin + (vBoxHeight - vText.Height)/2;
        }

        Color getColor()
        {
            Color c = Color.Black;
            if (config.color.Equals(colors[0])) c = Color.White;
            else if (config.color.Equals(colors[2])) c = Color.LightGray;
            else if (config.color.Equals(colors[3])) c = Color.Gray;
            else if (config.color.Equals(colors[4])) c = Color.DarkGray;
            else if (config.color.Equals(colors[5])) c = Color.Red;
            else if (config.color.Equals(colors[6])) c = Color.Green;
            else if (config.color.Equals(colors[7])) c = Color.Blue;
            else if (config.color.Equals(colors[8])) c = Color.Yellow;
            return c;
        }

        // Div., mimic getColor to write getBColor for bkgnd	
        Color getBColor()
        {
            if (config.bcolor==null || config.bcolor.Equals(bcolors[0])) return Color.Empty;
            Color bc = Color.White;
            if (config.bcolor.Equals(bcolors[1])) bc = Color.Black;
            else if (config.bcolor.Equals(bcolors[3])) bc = Color.DarkGray;
            else if (config.bcolor.Equals(bcolors[4])) bc = Color.Gray;
            else if (config.bcolor.Equals(bcolors[5])) bc = Color.LightGray;
            else if (config.bcolor.Equals(bcolors[6])) bc = Color.Yellow;
            else if (config.bcolor.Equals(bcolors[7])) bc = Color.Blue;
            else if (config.bcolor.Equals(bcolors[8])) bc = Color.Green;
            else if (config.bcolor.Equals(bcolors[9])) bc = Color.Red;
            return bc;
        }

        /**
         * Draw the scale bar, based on the current configuration.
         * 
         * If {previewOnly} is true, only the active slice will be
         * labeled with a scalebar. If it is false, all slices of
         * the stack will be labeled.
         * 
         * This method chooses whether to use an overlay or the
         * drawing tool to create the scalebar.
         */
        public void updateScalebar(bool previewOnly)
        {
            removeScalebar();

            Overlay scaleBarOverlay;
            try
            {
                scaleBarOverlay = createScaleBarOverlay();
            }
            catch (MissingRoiException e)
            {
                return; // Simply don't draw the scalebar.
            }

            Overlay impOverlay = imp.getOverlay();
            if (impOverlay==null)
            {
                impOverlay = new Overlay();
            }

            if (config.useOverlay)
            {
                foreach (Roi roi in scaleBarOverlay)
				    impOverlay.add(roi);
                imp.setOverlay(impOverlay);
		    } 
            else
            {
                if (previewOnly)
                {
                    ImageProcessor ip = imp.getProcessor();
                    drawOverlayOnProcessor(scaleBarOverlay, ip);
                    imp.updateAndDraw();
                }
                else
                {
                    ImageStack stack = imp.getStack();
                    for (int i = 1; i<=stack.size(); i++)
                    {
                        ImageProcessor ip = stack.getProcessor(i);
                        drawOverlayOnProcessor(scaleBarOverlay, ip);
                        imp.updateAndDraw();
                    }
                    imp.setStack(stack);
                }
            }
	    }

        void drawOverlayOnProcessor(Overlay overlay, ImageProcessor processor)
        {
            if (processor.getBitDepth() == 8 || processor.getBitDepth() == 24)
            {
                // drawOverlay() only works for 8-bits and RGB
                processor.drawOverlay(overlay);
                return;
            }
            // Generate a buffer ImageProcessor, completely black.
            // We will draw the overlay on it, then loop over each pixel,
            // to replace drawn pixels in the original processor.
            ImageProcessor ip = new ByteProcessor(imp.getWidth(), imp.getHeight());
            ip.setCalibrationTable(processor.getCalibrationTable());
            LUT lut = ip.getLut();
            foreach (Roi roi in overlay) {
                Color fillColor = roi.getFillColor();
                if (fillColor != null)
                {
                    int i = processor.getBestIndex(fillColor);
                    roi.setFillColor(Color.FromArgb(lut.getRed(i), lut.getGreen(i), lut.getBlue(i)));
                    // Below, when looping on each pixel of the buffer, we detect whether it was
                    // drawn by checking if the pixel value is greater than zero. Hence, we cannot
                    // put zero-valued pixels when the Roi is black, or these pixels will not
                    // be part of the drawing.
                    if (roi.getFillColor().Equals(Color.Black)) roi.setFillColor(Color.FromArgb(1,1,1));
                }
                Color strokeColor = roi.getStrokeColor();
                if (strokeColor != null)
                {
                    int i = processor.getBestIndex(strokeColor);
                    roi.setStrokeColor(Color.FromArgb(lut.getRed(i), lut.getGreen(i), lut.getBlue(i)));
                    if (roi.getStrokeColor().Equals(Color.Black)) roi.setStrokeColor(Color.FromArgb(1, 1, 1));
                }
            }
            ip.drawOverlay(overlay);
            for (int y = 0; y < ip.getHeight(); y++)
            {
                for (int x = 0; x < ip.getWidth(); x++)
                {
                    int p = ip.get(x, y);
                    if (p > 0)
                    {
                        p = (int)Math.Round(p * ((processor.getMax() - processor.getMin()) / 255d) +
                                            (float)processor.getMin());
                        if (processor.getBitDepth() == 32)
                            p = BitConverter.SingleToInt32Bits(p);
                        processor.putPixel(x, y, p);
                    }
                }
            }
        }

    } //ScaleBar class

    class BarDialog : GenericDialog
    {

        public BarDialog(String hUnits, String vUnits, int hDigits, int vDigits, bool multipleSlices, ScaleBar scaleBar) : base("Scale Bar")
        {

            addNumericField("Width in "+hUnits+": ", scaleBar.config.hBarWidth, hDigits);
            addNumericField("Height in "+vUnits+": ", scaleBar.config.vBarHeight, vDigits);
            addNumericField("Thickness in pixels: ", scaleBar.config.barThicknessInPixels, 0);
            addNumericField("Font size: ", scaleBar.config.fontSize, 0);
            addChoice("Color: ", scaleBar.colors, scaleBar.config.color);
            addChoice("Background: ", scaleBar.bcolors, scaleBar.config.bcolor);
            addChoice("Location: ", scaleBar.locations, scaleBar.config.location);
            scaleBar.checkboxStates[0] = scaleBar.config.showHorizontal; scaleBar.checkboxStates[1] = scaleBar.config.showVertical;
            scaleBar.checkboxStates[2] = scaleBar.config.boldText; scaleBar.checkboxStates[3] = scaleBar.config.hideText;
            scaleBar.checkboxStates[4] = scaleBar.config.serifFont; scaleBar.checkboxStates[5] = scaleBar.config.useOverlay;
            setInsets(10, 25, 0);
            addCheckboxGroup(3, 2, scaleBar.checkboxLabels, scaleBar.checkboxStates);

            // For simplicity of the itemStateChanged() method below,
            // is is best to keep the "Label all slices" checkbox in
            // the last position.
            if (multipleSlices)
            {
                setInsets(0, 25, 0);
                addCheckbox("Label all slices", scaleBar.config.labelAll);
            }
        }
    } //BarDialog inner class


    class BarDialogListener : DialogListener
    {

        bool multipleSlices;
        private ScaleBar _scaleBar;

        public BarDialogListener(bool multipleSlices, ScaleBar scaleBar):base()
        {
            _scaleBar = scaleBar;
            this.multipleSlices = multipleSlices;
        }

        public bool dialogItemChanged(GenericDialog gd, EventArgs e)
        {
            _scaleBar.config.hBarWidth = gd.getNextNumber();
            _scaleBar.config.vBarHeight = gd.getNextNumber();
            _scaleBar.config.barThicknessInPixels = (int)gd.getNextNumber();
            _scaleBar.config.fontSize = (int)gd.getNextNumber();
            _scaleBar.config.color = gd.getNextChoice();
            _scaleBar.config.bcolor = gd.getNextChoice();
            _scaleBar.config.location = gd.getNextChoice();
            _scaleBar.config.showHorizontal = gd.getNextBoolean();
            _scaleBar.config.showVertical = gd.getNextBoolean();
            _scaleBar.config.boldText = gd.getNextBoolean();
            _scaleBar.config.hideText = gd.getNextBoolean();
            _scaleBar.config.serifFont = gd.getNextBoolean();
            _scaleBar.config.useOverlay = gd.getNextBoolean();
            if (multipleSlices)
                _scaleBar.config.labelAll = gd.getNextBoolean();
            if (!_scaleBar.config.showHorizontal && !_scaleBar.config.showVertical && IJ.isMacro())
            {
                // Previous versions of this plugin did not handle vertical scale bars:
                // the macro syntax was different in that "height" meant "thickness" of
                // the horizontal scalebar.
                // If the conditional above is true, then the macro syntax is the old
                // one, so we swap a few config variables.
                _scaleBar.config.showHorizontal = true;
                _scaleBar.config.barThicknessInPixels = (int)_scaleBar.config.vBarHeight;
                _scaleBar.config.vBarHeight = 0.0;
            }

            string widthString = ((TextBox)gd.getNumericFields()[0]).Text;
            bool hasDecimalPoint = false;
            _scaleBar.config.hDigits = 0;
            for (int i = 0; i < widthString.Length; i++)
            {
                if (hasDecimalPoint)
                    _scaleBar.config.hDigits += 1;
                if (widthString[i] == '.')
                    hasDecimalPoint = true;
            }

            String heightString = ((TextBox)gd.getNumericFields()[1]).Text;
            hasDecimalPoint = false;
            _scaleBar.config.vDigits = 0;
            for (int i = 0; i < heightString.Length; i++)
            {
                if (hasDecimalPoint)
                    _scaleBar.config.vDigits += 1;
                if (heightString[i] == '.')
                    hasDecimalPoint = true;
            }

            _scaleBar.updateScalebar(true);
            return true;
        }
    }

    class MissingRoiException : Exception
    {
        public MissingRoiException():base("Scalebar location is set to AT_SELECTION but there is no selection on the image.") {
            
        }
    } //MissingRoiException inner class

    public class ScaleBarConfiguration
    {

        private static int defaultBarHeight = 4;

        public bool showHorizontal;
        public bool showVertical;
        public double hBarWidth;
        public double vBarHeight;
        public int hDigits;  // The number of digits after the decimal point that the user input in the dialog for vBarWidth.
        public int vDigits;
        public int barThicknessInPixels;
        public string location;
        public string color;
        public string bcolor;
        public bool boldText;
        public bool hideText;
        public bool serifFont;
        public bool useOverlay;
        public int fontSize;
        public bool labelAll;

        private ScaleBar _scaleBar;

        /**
         * Create ScaleBarConfiguration with default values.
         */
        public ScaleBarConfiguration(ScaleBar scaleBar)
        {
            _scaleBar = scaleBar;
            this.showHorizontal = true;
            this.showVertical = false;
            this.hBarWidth = -1;
            this.vBarHeight = -1;
            this.barThicknessInPixels = defaultBarHeight;
            this.location = _scaleBar.locations[ScaleBar.LOWER_RIGHT];
            this.color = _scaleBar.colors[0];
            this.bcolor = _scaleBar.bcolors[0];
            this.boldText = true;
            this.hideText = false;
            this.serifFont = false;
            this.useOverlay = true;
            this.fontSize = 14;
            this.labelAll = false;
        }

        /**
         * Copy constructor.
         */
        public ScaleBarConfiguration(ScaleBarConfiguration model, ScaleBar scaleBar)
        {
            _scaleBar = scaleBar;
            this.updateFrom(model);
        }

        public void updateFrom(ScaleBarConfiguration model)
        {
            this.showHorizontal = model.showHorizontal;
            this.showVertical = model.showVertical;
            this.hBarWidth = model.hBarWidth;
            this.vBarHeight = model.vBarHeight;
            this.hDigits = model.hDigits;
            this.vDigits = model.vDigits;
            this.barThicknessInPixels = model.barThicknessInPixels;
            this.location = _scaleBar.locations[ScaleBar.LOWER_RIGHT];
            this.color = model.color;
            this.bcolor = model.bcolor;
            this.boldText = model.boldText;
            this.serifFont = model.serifFont;
            this.hideText = model.hideText;
            this.useOverlay = model.useOverlay;
            this.fontSize = model.fontSize;
            this.labelAll = model.labelAll;
        }
    } //ScaleBarConfiguration inner class
}
